﻿using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace System
{
    public static class ObjectExtentions
    {
        public static int GetHashCode(params object[] Objects) { return Objects.GetComplexHashCode(); }
        public static int GetComplexHashCode(this object[] Objects)
        {
            unchecked
            {
                var result = 397;
                for(var i = 0; i < Objects.Length; i++)
                    result = (result * 397) ^ Objects[i].GetHashCode();
                return result;
            }
        }

        [Diagnostics.DebuggerStepThrough]
        public static TOut ConvertObjectTo<TIn, TOut>(this TIn o, Converter<TIn, TOut> Converter) { return Converter(o); }

        [Diagnostics.DebuggerStepThrough]
        public static TAttribute GetObjectAttribute<TAttribute>(this object o, bool Inherited = false)
            where TAttribute : Attribute
        {
            var objectAttributes = o.GetObjectAttributes<TAttribute>(Inherited);
            return objectAttributes.Length != 0 ? objectAttributes[0] : default(TAttribute);
        }

        [Diagnostics.DebuggerStepThrough]
        public static TAttribute[] GetObjectAttributes<TAttribute>(this object o, bool Inherited = false)
            where TAttribute : Attribute
        {
            return o.GetType().GetCustomAttributes<TAttribute>(Inherited);
        }

        [Diagnostics.DebuggerStepThrough]
        public static bool IsNotNull(this object o) { return !o.IsNull(); }

        [Diagnostics.DebuggerStepThrough]
        public static bool IsNull(this object o) { return ReferenceEquals(o, null); }

        [Diagnostics.DebuggerStepThrough]
        public static TAttribute[] GetAttributes<TAttribute, TObject>(this TObject o, bool Inherited = false)
            where TAttribute : Attribute
        {
            return typeof(TObject).GetCustomAttributes<TAttribute>(Inherited);
        }

        /// <summary>Инициализировать объект ссылочного типа</summary>
        /// <typeparam name="TObject">Тип объекта</typeparam>
        /// <param name="obj">Инициализируемый объект</param>
        /// <param name="Initializator">Метод инициализации</param>
        /// <returns>Инициализированный объект</returns>
        [Diagnostics.DebuggerStepThrough]
        public static TObject InitializeObject<TObject>(this TObject obj, Action<TObject> Initializator) where TObject : class
        {
            Contract.Requires(obj != null);
            Contract.Ensures(Contract.Result<TObject>() != null);
            Contract.Ensures(Contract.Result<TObject>() == obj);
            if(Initializator != null) Initializator(obj);
            return obj;
        }
    }
}

namespace System.Tags
{
    public static class TagExtentions
    {
        [Diagnostics.DebuggerStepThrough]
        public static TTag GetTag<TTag>(this object o)
        {
            return TagPool.Tag<TTag>(o);
        }

        [Diagnostics.DebuggerStepThrough]
        public static void SetTag<TTag>(this object o, TTag Tag)
        {
            TagPool.SetTag(o, Tag);
        }

        #region Nested type: TagPool

        private static class TagPool
        {
            private static readonly object sf_Lock = new object();
            private static Dictionary<WeakReference, Dictionary<Type, object>> sf_Tags;

            //static TagPool()
            //{
            //}

            public static void SetTag(object o, object Tag)
            {
                lock(sf_Lock)
                {
                    var tags = sf_Tags ?? (sf_Tags = new Dictionary<WeakReference, Dictionary<Type, object>>());

                    tags.Keys.Where(w => !w.IsAlive).ToArray().Foreach(w => tags.Remove(w));
                    Predicate<WeakReference> selector = w => o.Equals(w.Target);

                    var reference = tags.Keys.Find(selector);
                    Dictionary<Type, object> dictionary;
                    if(reference != null)
                        dictionary = tags[reference];
                    else
                        tags.Add(new WeakReference(o), dictionary = new Dictionary<Type, object>());

                    var type = Tag.GetType();

                    if(dictionary.Keys.Contains(type))
                        dictionary[type] = Tag;
                    else
                        dictionary.Add(type, Tag);
                }
            }

            public static TTagType Tag<TTagType>(object o)
            {
                lock(sf_Lock)
                {
                    object obj2;
                    if(sf_Tags == null)
                        return default(TTagType);

                    var tags = sf_Tags;
                    Func<WeakReference, bool> func = w => !w.IsAlive;
                    tags.Keys.Where(func).Foreach(w => tags.Remove(w));
                    Predicate<WeakReference> selector = w => o.Equals(w.Target);
                    var reference = tags.Keys.Find(selector);
                    if(reference == null)
                        return default(TTagType);
                    var dictionary = tags[reference];
                    if(!dictionary.TryGetValue(typeof(TTagType), out obj2))
                        return default(TTagType);
                    return (TTagType)obj2;
                }
            }
        }

        #endregion
    }
}